#include <errno.h>
#include <signal.h>
#include "thread.h"
#include "redobject.h"
#include "redboolobject.h"
#include "rednullobject.h"
#include "redtupleobject.h"
#include "redexceptionobject.h"

/* thread */

typedef struct _RedThreadObject
{
	RedObject_HEAD

	char		 daemon;
	RedThread	*thread;
	RedObject	*callable;
	RedObject	*arguments;
} RedThreadObject;

#define RedThread_Daemon(o)			(((RedThreadObject *)(o))->daemon)
#define RedThread_Thread(o)			(((RedThreadObject *)(o))->thread)
#define RedThread_Callable(o)		(((RedThreadObject *)(o))->callable)
#define RedThread_Arguments(o)		(((RedThreadObject *)(o))->arguments)

static char thread_init(RedObject *self, RedObject *args)
{
	if (!Red_INIT())
		return 0;

	RedObject *callable;
	RedObject *arguments;

	if (!RedTuple_Unpack(args, "C>", &callable, &arguments))
		return 0;

	Red_REF(callable);
	RedThread_Daemon(self) = 0;
	RedThread_Thread(self) = NULL;
	RedThread_Callable(self) = callable;
	RedThread_Arguments(self) = arguments;
	return 1;
}

static void thread_clear(RedObject *self)
{
	Red_UNREF(RedThread_Thread(self));
	Red_UNREF(RedThread_Callable(self));
	Red_UNREF(RedThread_Arguments(self));
}

static void thread_release(RedObject *self)
{
	thread_clear(self);
	Red_RELEASE();
}

static void thread_traverse(RedObject *self, tp_visit_fn_t visit, void *args)
{
	Red_VISIT(RedThread_Thread(self));
	Red_VISIT(RedThread_Callable(self));
	Red_VISIT(RedThread_Arguments(self));
}

static RedObject *thread_kill(RedObject *self, RedObject *args);
static RedObject *thread_join(RedObject *self, RedObject *args);
static RedObject *thread_start(RedObject *self, RedObject *args);
static RedObject *thread_daemon(RedObject *self, RedObject *args);
static RedObject *thread_isdaemon(RedObject *self, RedObject *args);

static RedMethodDesc thread_methods[] =
{
	{ "kill"	, thread_kill		},
	{ "join"	, thread_join		},
	{ "start"	, thread_start		},
	{ "daemon"	, thread_daemon		},
	{ "isdaemon", thread_isdaemon	},
	{ NULL, NULL },
};

static RedTypeObject RedType_Thread =
{
	RedType_INIT("thread", RedThreadObject, RedType_Object)

	.tp_init		= thread_init,
	.tp_clear		= thread_clear,
	.tp_release		= thread_release,
	.tp_traverse	= thread_traverse,

	.tp_methods		= thread_methods,
};

static RedObject *thread_proc(RedObject *self)
{
	RedObject *callable = RedThread_Callable(self);
	RedObject *arguments = RedThread_Arguments(self);
	return RedObject_Invoke(callable, arguments);
}

static RedObject *thread_kill(RedObject *self, RedObject *args)
{
	if (self->ob_type != &RedType_Thread)
	{
		RedException_ThrowFormat(&RedExc_TypeError, "`Thread` object expected for `self` argument");
		return NULL;
	}

	if (!RedThread_Thread(self))
	{
		RedException_ThrowFormat(&RedExc_OSError, "Thread not started");
		return NULL;
	}

	int ret;
	int64_t sig;

	if (!RedTuple_Unpack(args, "i*", &sig))
		return NULL;

	if (!(ret = pthread_kill(RedThread_Thread(self)->thread, (int)sig)))
		Red_Return_Null();

	errno = ret;
	RedException_ThrowErrno(&RedExc_OSError);
	return NULL;
}

static RedObject *thread_join(RedObject *self, RedObject *args)
{
	if (self->ob_type != &RedType_Thread)
	{
		RedException_ThrowFormat(&RedExc_TypeError, "`Thread` object expected for `self` argument");
		return NULL;
	}

	if (!RedThread_Thread(self))
	{
		RedException_ThrowFormat(&RedExc_OSError, "Thread not started");
		return NULL;
	}

	RedObject *retval = RedThread_Join(RedThread_Thread(self));

	if (!retval && !RedThread_Daemon(self))
	{
		RedException_ThrowFormat(&RedExc_OSError, "pthread_join() failed");
		return NULL;
	}

	Red_UNREF(RedThread_Thread(self));
	return retval;
}

static RedObject *thread_start(RedObject *self, RedObject *args)
{
	if (self->ob_type != &RedType_Thread)
	{
		RedException_ThrowFormat(&RedExc_TypeError, "`Thread` object expected for `self` argument");
		return NULL;
	}

	RedThread_Thread(self) = RedThread_Create(thread_proc, self, RedThread_Daemon(self));
	return RedThread_Thread(self) ? (Red_REF(Red_Null), Red_Null) : NULL;
}

static RedObject *thread_daemon(RedObject *self, RedObject *args)
{
	if (self->ob_type != &RedType_Thread)
	{
		RedException_ThrowFormat(&RedExc_TypeError, "`Thread` object expected for `self` argument");
		return NULL;
	}

	char daemon;
	if (!RedTuple_Unpack(args, "b*", &daemon))
		return NULL;

	if (RedThread_Thread(self))
	{
		if (!daemon && RedThread_Daemon(self))
		{
			RedException_ThrowFormat(&RedExc_OSError, "Cannot re-attach already running thread");
			return NULL;
		}

		if (daemon && !RedThread_Daemon(self))
		{
			/* detach if already running */
			RedThread_Thread(self)->daemon = 1;
			pthread_detach(RedThread_Thread(self)->thread);
		}
	}

	RedThread_Daemon(self) = daemon;
	Red_Return_Null();
}

static RedObject *thread_isdaemon(RedObject *self, RedObject *args)
{
	if (self->ob_type != &RedType_Thread)
	{
		RedException_ThrowFormat(&RedExc_TypeError, "`Thread` object expected for `self` argument");
		return NULL;
	}

	if (RedThread_Daemon(self))
		Red_Return_True();
	else
		Red_Return_False();
}

/* spin lock */

typedef struct _RedSpinObject
{
	RedObject_HEAD
	RedSpin spin;
} RedSpinObject;

#define RedSpin_Spin(o)		(&(((RedSpinObject *)(o))->spin))

static RedObject *spin_acquire(RedObject *self, RedObject *args)
{
	if (!RedTuple_Unpack(args, ""))
		return NULL;

	RedSpin_Acquire(RedSpin_Spin(self));
	Red_Return_Null();
}

static RedObject *spin_release(RedObject *self, RedObject *args)
{
	if (!RedTuple_Unpack(args, ""))
		return NULL;

	RedSpin_Release(RedSpin_Spin(self));
	Red_Return_Null();
}

static RedMethodDesc spin_methods[] =
{
	{ "acquire", spin_acquire },
	{ "release", spin_release },
	{ NULL, NULL },
};

static RedTypeObject RedType_Spin =
{
	RedType_INIT("spin", RedSpinObject, RedType_Object)
	.tp_methods = spin_methods,
};

/* mutex */

typedef struct _RedMutexObject
{
	RedObject_HEAD
	RedMutex mutex;
} RedMutexObject;

#define RedMutex_Mutex(o)		(&(((RedMutexObject *)(o))->mutex))

static char mutex_init(RedObject *self, RedObject *args)
{
	if (!Red_INIT() || !RedTuple_Unpack(args, ""))
		return 0;

	RedMutex_Init(RedMutex_Mutex(self));
	return 1;
}

static void mutex_release(RedObject *self)
{
	RedMutex_Free(RedMutex_Mutex(self));
	Red_RELEASE();
}

static RedObject *mutex_acquire(RedObject *self, RedObject *args)
{
	if (!RedTuple_Unpack(args, ""))
		return NULL;

	RedMutex_Acquire(RedMutex_Mutex(self));
	Red_Return_Null();
}

static RedObject *mutex_release_(RedObject *self, RedObject *args)
{
	if (!RedTuple_Unpack(args, ""))
		return NULL;

	RedMutex_Release(RedMutex_Mutex(self));
	Red_Return_Null();
}

static RedMethodDesc mutex_methods[] =
{
	{ "acquire", mutex_acquire  },
	{ "release", mutex_release_ },
	{ NULL, NULL },
};

static RedTypeObject RedType_Mutex =
{
	RedType_INIT("mutex", RedMutexObject, RedType_Object)

	.tp_init	= mutex_init,
	.tp_release	= mutex_release,
	.tp_methods	= mutex_methods,
};

/* semaphore */

typedef struct _RedSemaphoreObject
{
	RedObject_HEAD
	RedSemaphore semaphore;
} RedSemaphoreObject;

#define RedSem_Sem(o)		(&(((RedSemaphoreObject *)(o))->semaphore))

static char semaphore_init(RedObject *self, RedObject *args)
{
	if (!Red_INIT())
		return 0;

	int64_t count;

	if (!RedTuple_Unpack(args, "i*", &count))
		return 0;

	RedSemaphore_Init(RedSem_Sem(self), (int)count);
	return 1;
}

static void semaphore_release(RedObject *self)
{
	RedSemaphore_Free(RedSem_Sem(self));
	Red_RELEASE();
}

static RedObject *semaphore_wait(RedObject *self, RedObject *args)
{
	if (!RedTuple_Unpack(args, ""))
		return NULL;

	RedSemaphore_Wait(RedSem_Sem(self));
	Red_Return_Null();
}

static RedObject *semaphore_signal(RedObject *self, RedObject *args)
{
	if (!RedTuple_Unpack(args, ""))
		return NULL;

	RedSemaphore_Signal(RedSem_Sem(self));
	Red_Return_Null();
}

static RedMethodDesc semaphore_methods[] =
{
	{ "wait"	, semaphore_wait	},
	{ "signal"	, semaphore_signal	},
	{ NULL, NULL },
};

static RedTypeObject RedType_Semaphore =
{
	RedType_INIT("semaphore", RedSemaphore, RedType_Object)

	.tp_init	= semaphore_init,
	.tp_release	= semaphore_release,
	.tp_methods	= semaphore_methods,
};

/* module entry */

static void module_exit(void *args)
{
	RedType_Cleanup(&RedType_Spin);
	RedType_Cleanup(&RedType_Mutex);
	RedType_Cleanup(&RedType_Thread);
	RedType_Cleanup(&RedType_Semaphore);
}

RED_EXPORT char module_entry(RedObject *self, RedObject *name)
{
	if (!RedType_Prepare(&RedType_Spin)) return 0;
	if (!RedType_Prepare(&RedType_Mutex)) return 0;
	if (!RedType_Prepare(&RedType_Thread)) return 0;
	if (!RedType_Prepare(&RedType_Semaphore)) return 0;

	RedType_Init(&RedType_Spin);
	RedType_Init(&RedType_Mutex);
	RedType_Init(&RedType_Thread);
	RedType_Init(&RedType_Semaphore);
	Red_ExitFunction(module_exit, NULL);

	if (!RedObject_AddConst(self, "Spin", (RedObject *)(&RedType_Spin))) return 0;
	if (!RedObject_AddConst(self, "Mutex", (RedObject *)(&RedType_Mutex))) return 0;
	if (!RedObject_AddConst(self, "Thread", (RedObject *)(&RedType_Thread))) return 0;
	if (!RedObject_AddConst(self, "Semaphore", (RedObject *)(&RedType_Semaphore))) return 0;

	return 1;
}
